﻿using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TarManager
{
    /// <summary>
    /// 压缩文件辅助类
    /// </summary>
    public class ZipHelper
    {
        #region 压缩文件
        /// <summary>
        /// 压缩单个文件
        /// </summary>
        /// <param name="FileToZip">需要压缩的文件（绝对路径）</param>
        /// <param name="ZipedPath">压缩后的文件路径（绝对路径）</param>
        /// <param name="ZipedFileName">压缩后的文件名称（文件名，默认 同源文件同名）</param>
        /// <param name="CompressionLevel">压缩等级（0 无 - 9 最高，默认 5）</param>
        /// <param name="BlockSize">缓存大小（每次写入文件大小，默认 2048）</param>
        /// <param name="PassWord">加密密码（默认 123456）</param>
        /// <param name="IsEncrypt">是否加密（默认 不加密）</param>
        public static void ZipFile(string FileToZip, string ZipedPath, string ZipedFileName = "", int CompressionLevel = 5, int BlockSize = 2048, string PassWord = "123456", bool IsEncrypt = false)
        {
            //如果文件没有找到，则报错
            if (!System.IO.File.Exists(FileToZip))
            {
                throw new System.IO.FileNotFoundException("指定要压缩的文件: " + FileToZip + " 不存在!");
            }
            //文件名称（默认同源文件名称相同）
            string ZipFileName = string.IsNullOrEmpty(ZipedFileName) ? ZipedPath + "\\" + new FileInfo(FileToZip).Name.Substring(0, new FileInfo(FileToZip).Name.LastIndexOf('.')) + ".zip" : ZipedPath + "\\" + ZipedFileName + ".zip";
            using (System.IO.FileStream ZipFile = System.IO.File.Create(ZipFileName))
            {
                using (ZipOutputStream ZipStream = new ZipOutputStream(ZipFile))
                {
                    using (System.IO.FileStream StreamToZip = new System.IO.FileStream(FileToZip, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                    {
                        string fileName = FileToZip.Substring(FileToZip.LastIndexOf("\\") + 1);
                        ZipEntry ZipEntry = new ZipEntry(fileName);
                        if (IsEncrypt)
                        {
                            //压缩文件加密
                            ZipStream.Password = PassWord;
                        }
                        ZipStream.PutNextEntry(ZipEntry);
                        //设置压缩级别
                        ZipStream.SetLevel(CompressionLevel);
                        //缓存大小
                        byte[] buffer = new byte[BlockSize];
                        int sizeRead = 0;
                        try
                        {
                            do
                            {
                                sizeRead = StreamToZip.Read(buffer, 0, buffer.Length);
                                ZipStream.Write(buffer, 0, sizeRead);
                            }
                            while (sizeRead > 0);
                        }
                        catch (System.Exception ex)
                        {
                            throw ex;
                        }
                        StreamToZip.Close();
                    }
                    ZipStream.Finish();
                    ZipStream.Close();
                }
                ZipFile.Close();
            }
        }
        /// <summary> 
        /// 压缩多个文件
        /// </summary>
        /// <param name="filePathList">要进行压缩的文件路径</param>
        /// <param name="fileNameList">要进行压缩的文件名</param>
        /// <param name="zipedFilePath">压缩后生成的压缩所在文件夹</param>
        /// <param name="zipedFileName">压缩后生成的压缩文件名</param>
        /// <param name="Level">压缩等级（0 无 - 9 最高，默认 5）</param>
        public static void ZipFileList(List<string> filePathList, List<string> fileNameList, string zipedFilePath, string zipedFileName, int Level = 5)
        {
            FileNameListZip fileNameListZip = GetFileName(filePathList, fileNameList, zipedFilePath, zipedFileName);
            using (FileStream ZipFile = System.IO.File.Create(fileNameListZip.fileZipName))
            {
                using (ZipOutputStream ZipStream = new ZipOutputStream(ZipFile))
                {
                    try
                    {
                        foreach (var fileToZip in fileNameListZip.fileDictionary)
                        {
                            string fileName = fileToZip.Value;
                            using (FileStream fs = System.IO.File.OpenRead(fileToZip.Key))
                            {
                                byte[] buffer = new byte[fs.Length];
                                fs.Read(buffer, 0, buffer.Length);
                                fs.Close();
                                ZipEntry ZipEntry = new ZipEntry(fileName);
                                ZipStream.PutNextEntry(ZipEntry);
                                ZipStream.SetLevel(Level);
                                ZipStream.Write(buffer, 0, buffer.Length);
                            }
                        }
                    }
                    finally
                    {
                        if (ZipStream != null)
                        {
                            ZipStream.Dispose();
                            ZipStream.Finish();
                            ZipStream.Close();
                        }
                    }
                }
            }

        }
        /// <summary>
        /// 压缩文件夹
        /// </summary>
        /// <param name="DirectoryToZip">需要压缩的文件夹（绝对路径）</param>
        /// <param name="ZipedPath">压缩后的文件路径（绝对路径）</param>
        /// <param name="ZipedFileName">压缩后的文件名称（文件名，默认 同源文件夹同名）</param>
        /// <param name="PassWord">压缩加密密码（默认 123456）</param>
        /// <param name="IsEncrypt">是否加密（默认 不加密）</param>
        public static void ZipDirectory(string DirectoryToZip, string ZipedPath, string ZipedFileName = "", string PassWord = "123456", bool IsEncrypt = false)
        {
            //如果目录不存在，则报错
            if (!System.IO.Directory.Exists(DirectoryToZip))
            {
                throw new System.IO.FileNotFoundException("指定的目录: " + DirectoryToZip + " 不存在!");
            }
            //文件名称（默认同源文件名称相同）
            string ZipFileName = string.IsNullOrEmpty(ZipedFileName) ? ZipedPath + "\\" + new DirectoryInfo(DirectoryToZip).Name + ".zip" : ZipedPath + "\\" + ZipedFileName + ".zip";

            System.IO.FileStream ZipFile = System.IO.File.Create(ZipFileName);
            ZipOutputStream s = new ZipOutputStream(ZipFile);
            if (IsEncrypt)
            {
                //压缩文件加密
                s.Password = PassWord;
            }
            ZipSetp(DirectoryToZip, s, "");
        }
        /// <summary>
        /// 压缩文件(并保存至服务器)
        /// </summary>
        /// <param name="filePathList">要压缩的所有文件（完全路径)</param>
        /// <param name="fileNameList">要压缩名称名称</param>
        /// <param name="zipedFileName">压缩后文件夹</param>
        /// <param name="zipedFileName">压缩后文件名称</param>
        /// <param name="Level">压缩级别</param>
        public static bool ZipFileMain(List<string> filePathList, List<string> fileNameList, string zipedFilePath, string zipedFileName, int Level = 5)
        {
            FileNameListZip fileNameListZip = GetFileName(filePathList, fileNameList, zipedFilePath, zipedFileName);
            ZipOutputStream s = new ZipOutputStream(System.IO.File.Create(fileNameListZip.fileZipName));
            Crc32 crc = new Crc32();
            //压缩级别
            s.SetLevel(Level);
            try
            {
                foreach (var file in fileNameListZip.fileDictionary)
                {
                    //打开压缩文件
                    FileStream fs = System.IO.File.OpenRead(file.Key);
                    byte[] buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, buffer.Length);
                    //建立压缩实体
                    ZipEntry entry = new ZipEntry(System.IO.Path.GetFileName(file.Value));
                    //时间
                    entry.DateTime = DateTime.Now;
                    //空间大小
                    entry.Size = fs.Length;
                    fs.Close();
                    crc.Reset();
                    crc.Update(buffer);
                    entry.Crc = crc.Value;
                    s.PutNextEntry(entry);
                    s.Write(buffer, 0, buffer.Length);
                }
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
            finally
            {
                s.Finish();
                s.Close();
            }
        }
        /// <summary>
        /// 压缩多个文件/文件夹
        /// </summary>
        /// <param name="sourceList">源文件/文件夹路径列表</param>
        /// <param name="zipFilePath">压缩文件路径</param>
        /// <param name="comment">注释信息</param>
        /// <param name="password">压缩密码</param>
        /// <param name="compressionLevel">压缩等级，范围从0到9，可选，默认为6</param>
        /// <param name="BufferSize">分段大小默认1024</param>
        /// <returns></returns>
        public static bool CompressFile(IEnumerable<string> sourceList, string zipFilePath, string comment = null, string password = null, int compressionLevel = 6, int BufferSize = 1024)
        {
            bool result = false;
            try
            {
                //检测目标文件所属的文件夹是否存在，如果不存在则建立
                string zipFileDirectory = Path.GetDirectoryName(zipFilePath);
                if (!Directory.Exists(zipFileDirectory))
                {
                    Directory.CreateDirectory(zipFileDirectory);
                }
                Dictionary<string, string> dictionaryList = PrepareFileSystementities(sourceList);
                using (ZipOutputStream zipStream = new ZipOutputStream(System.IO.File.Create(zipFilePath)))
                {
                    zipStream.Password = password;//设置密码
                    zipStream.SetComment(comment);//添加注释
                    zipStream.SetLevel(compressionLevel);//设置压缩等级
                    foreach (string key in dictionaryList.Keys)//从字典取文件添加到压缩文件
                    {
                        if (System.IO.File.Exists(key))//判断是文件还是文件夹
                        {
                            FileInfo fileItem = new FileInfo(key);

                            using (FileStream readStream = fileItem.Open(FileMode.Open,
                                FileAccess.Read, FileShare.Read))
                            {
                                ZipEntry zipEntry = new ZipEntry(dictionaryList[key]);
                                zipEntry.DateTime = fileItem.LastWriteTime;
                                zipEntry.Size = readStream.Length;
                                zipStream.PutNextEntry(zipEntry);
                                int readLength = 0;
                                byte[] buffer = new byte[BufferSize];

                                do
                                {
                                    readLength = readStream.Read(buffer, 0, BufferSize);
                                    zipStream.Write(buffer, 0, readLength);
                                } while (readLength == BufferSize);

                                readStream.Close();
                            }
                        }
                        else//对文件夹的处理
                        {
                            ZipEntry zipEntry = new ZipEntry(dictionaryList[key] + "/");
                            zipStream.PutNextEntry(zipEntry);
                        }
                    }
                    zipStream.Flush();
                    zipStream.Finish();
                    zipStream.Close();
                }
                result = true;
            }
            catch (System.Exception ex)
            {
                throw new Exception("压缩文件失败", ex);
            }
            return result;
        }
        #endregion 压缩文件

        #region 解压
        /// <summary>
        /// 解压一个zip文件
        /// </summary>
        /// <param name="ZipFile">需要解压的Zip文件（绝对路径）</param>
        /// <param name="TargetDirectory">解压到的目录</param>
        /// <param name="Password">解压密码</param>
        /// <param name="OverWrite">是否覆盖已存在的文件</param>
        public static void UnZip(string ZipFile, string TargetDirectory, string Password, bool OverWrite = true)
        {
            //如果解压到的目录不存在，则报错
            if (!System.IO.Directory.Exists(TargetDirectory))
            {
                throw new System.IO.FileNotFoundException("指定的目录: " + TargetDirectory + " 不存在!");
            }
            //目录结尾
            if (!TargetDirectory.EndsWith("\\")) { TargetDirectory = TargetDirectory + "\\"; }
            ZipInputStream zipfiles = new ZipInputStream(System.IO.File.OpenRead(ZipFile));
            if (!string.IsNullOrWhiteSpace(Password))
            {
                zipfiles.Password = Password;
            }
            ZipEntry theEntry;
            while ((theEntry = zipfiles.GetNextEntry()) != null)
            {
                string directoryName = "";
                string pathToZip = "";
                pathToZip = theEntry.Name;
                if (pathToZip != "")
                {
                    directoryName = Path.GetDirectoryName(pathToZip) + "\\";
                }
                string fileName = Path.GetFileName(pathToZip);
                Directory.CreateDirectory(TargetDirectory + directoryName);
                if (fileName != "")
                {
                    if ((System.IO.File.Exists(TargetDirectory + directoryName + fileName) && OverWrite) || (!System.IO.File.Exists(TargetDirectory + directoryName + fileName)))
                    {
                        using (FileStream streamWriter = System.IO.File.Create(TargetDirectory + directoryName + fileName))
                        {
                            int size = 2048;
                            byte[] data = new byte[2048];
                            while (true)
                            {
                                size = zipfiles.Read(data, 0, data.Length);

                                if (size > 0)
                                    streamWriter.Write(data, 0, size);
                                else
                                    break;
                            }
                            streamWriter.Close();
                        }
                    }
                }
            }
            zipfiles.Close();

        }
        /// <summary>
        /// 解压文件到指定文件夹
        /// </summary>
        /// <param name="sourceFile">压缩文件</param>
        /// <param name="destinationDirectory">目标文件夹，如果为空则解压到当前文件夹下</param>
        /// <param name="password">密码</param>
        /// <param name="BufferSize">分段大小</param>
        /// <returns></returns>
        public static bool DecomparessFile(string sourceFile, string destinationDirectory = null, string password = null, int BufferSize = 1024)
        {
            bool result = false;
            if (!System.IO.File.Exists(sourceFile))
            {
                throw new FileNotFoundException("要解压的文件不存在", sourceFile);
            }
            if (string.IsNullOrWhiteSpace(destinationDirectory))
            {
                destinationDirectory = Path.GetDirectoryName(sourceFile);
            }
            try
            {
                if (!Directory.Exists(destinationDirectory))
                {
                    Directory.CreateDirectory(destinationDirectory);
                }
                using (ZipInputStream zipStream = new ZipInputStream(System.IO.File.Open(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)))
                {
                    zipStream.Password = password;
                    ZipEntry zipEntry = zipStream.GetNextEntry();
                    while (zipEntry != null)
                    {
                        if (zipEntry.IsDirectory)//如果是文件夹则创建
                        {
                            Directory.CreateDirectory(Path.Combine(destinationDirectory,
                                Path.GetDirectoryName(zipEntry.Name)));
                        }
                        else
                        {
                            string fileName = Path.GetFileName(zipEntry.Name);
                            if (!string.IsNullOrEmpty(fileName) && fileName.Trim().Length > 0)
                            {
                                FileInfo fileItem = new FileInfo(Path.Combine(destinationDirectory, zipEntry.Name));
                                using (FileStream writeStream = fileItem.Create())
                                {
                                    byte[] buffer = new byte[BufferSize];
                                    int readLength = 0;
                                    do
                                    {
                                        readLength = zipStream.Read(buffer, 0, BufferSize);
                                        writeStream.Write(buffer, 0, readLength);
                                    } while (readLength == BufferSize);
                                    writeStream.Flush();
                                    writeStream.Close();
                                }
                                fileItem.LastWriteTime = zipEntry.DateTime;
                            }
                        }
                        zipEntry = zipStream.GetNextEntry();//获取下一个文件
                    }
                    zipStream.Close();
                }
                result = true;
            }
            catch (System.Exception ex)
            {
                throw new Exception("文件解压发生错误", ex);
            }
            return result;
        }
        /// <summary>
        /// 解压一个Rar文件
        /// </summary>
        /// <param name="RarFile">需要解压的Rar文件（绝对路径）</param>
        /// <param name="TargetDirectory">解压到的目录</param>
        /// <param name="OverWrite">是否覆盖已存在的文件</param>
        public static void UnRar(string RarFile, string TargetDirectory, bool OverWrite = true)
        {
            //如果解压到的目录不存在，则报错
            if (!System.IO.Directory.Exists(TargetDirectory))
            {
                throw new System.IO.FileNotFoundException("指定的目录: " + TargetDirectory + " 不存在!");
            }
            using (var archive = SharpCompress.Archives.Rar.RarArchive.Open(RarFile))
            {
                foreach (var entry in archive.Entries)
                {
                    if (!entry.IsDirectory)
                    {
                        entry.WriteToDirectory(TargetDirectory, new SharpCompress.Common.ExtractionOptions()
                        {
                            ExtractFullPath = true,
                            Overwrite = OverWrite
                        });
                    }
                }
            }
        }
        #endregion

        #region 辅助方法
        /// <summary>
        /// 处理文件名
        /// </summary>
        /// <param name="filePathList">文件地址集合</param>
        /// <param name="fileNameList">文件名集合</param>
        /// <param name="zipFilePath">压缩包地址</param>
        /// <param name="zipedFile">压缩包名称</param>
        /// <returns></returns>
        public static FileNameListZip GetFileName(List<string> filePathList, List<string> fileNameList, string zipFilePath, string zipedFile)
        {
            ///文件重名处理
            Dictionary<string, int> dictionary = new Dictionary<string, int>();
            //文件路径与文件名字典
            Dictionary<string, string> fileDictionary = new Dictionary<string, string>();
            string fileZipName = string.Empty;
            FileNameListZip fileNameListZip = new FileNameListZip();
            if (filePathList != null && filePathList.Count > 0)
            {
                for (int i = 0; i < filePathList.Count; i++)
                {
                    if (System.IO.File.Exists(filePathList[i]))
                    {
                        string filename = Path.GetFileName(fileNameList[i]);//返回带扩展名的文件名 "default.avi"
                        string extension = Path.GetExtension(fileNameList[i]);//扩展名 ".aspx"
                        string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileNameList[i]);// 没有扩展名的文件名 "default"
                        if (dictionary.ContainsKey(filename))
                        {
                            var count = dictionary[filename] + 1;
                            dictionary[filename] = count;
                            var name = fileNameWithoutExtension + "(" + count + ")" + extension;
                            fileDictionary.Add(filePathList[i], name);
                        }
                        else
                        {
                            dictionary.Add(filename, 1);
                            fileDictionary.Add(filePathList[i], filename);
                        }
                    }
                }
                fileNameListZip.fileDictionary = fileDictionary;
                if (!Directory.Exists(zipFilePath))
                {
                    Directory.CreateDirectory(zipFilePath);
                }
                fileNameListZip.fileZipName = zipFilePath + "\\" + zipedFile;
            }
            return fileNameListZip;
        }

        /// <summary>
        /// 递归遍历目录
        /// </summary>
        private static void ZipSetp(string strDirectory, ZipOutputStream s, string parentPath)
        {
            if (strDirectory[strDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                strDirectory += Path.DirectorySeparatorChar;
            }
            Crc32 crc = new Crc32();
            string[] filenames = Directory.GetFileSystemEntries(strDirectory);
            foreach (string file in filenames)// 遍历所有的文件和目录
            {
                if (Directory.Exists(file))// 先当作目录处理如果存在这个目录就递归Copy该目录下面的文件
                {
                    string pPath = parentPath;
                    pPath += file.Substring(file.LastIndexOf("\\") + 1);
                    pPath += "\\";
                    ZipSetp(file, s, pPath);
                }
                else // 否则直接压缩文件
                {
                    //打开压缩文件
                    using (FileStream fs = System.IO.File.OpenRead(file))
                    {
                        byte[] buffer = new byte[fs.Length];
                        fs.Read(buffer, 0, buffer.Length);
                        string fileName = parentPath + file.Substring(file.LastIndexOf("\\") + 1);
                        ZipEntry entry = new ZipEntry(fileName);
                        entry.DateTime = DateTime.Now;
                        entry.Size = fs.Length;
                        fs.Close();
                        crc.Reset();
                        crc.Update(buffer);
                        entry.Crc = crc.Value;
                        s.PutNextEntry(entry);
                        s.Write(buffer, 0, buffer.Length);
                    }
                }
            }
        }
        /// <summary>
        /// 为压缩准备文件系统对象
        /// </summary>
        /// <param name="sourceFileEntityPathList"></param>
        /// <returns></returns>
        private static Dictionary<string, string> PrepareFileSystementities(IEnumerable<string> sourceFileEntityPathList)
        {
            Dictionary<string, string> fileEntityDictionary = new Dictionary<string, string>();//文件字典
            string parentDirectoryPath = "";
            foreach (string fileEntityPath in sourceFileEntityPathList)
            {
                string path = fileEntityPath;
                //保证传入的文件夹也被压缩进文件
                if (path.EndsWith(@"\"))
                {
                    path = path.Remove(path.LastIndexOf(@"\"));
                }

                parentDirectoryPath = Path.GetDirectoryName(path) + @"\";

                if (parentDirectoryPath.EndsWith(@":\\"))//防止根目录下把盘符压入的错误
                {
                    parentDirectoryPath = parentDirectoryPath.Replace(@"\\", @"\");
                }

                //获取目录中所有的文件系统对象
                Dictionary<string, string> subDictionary = GetAllFileSystemEntities(path, parentDirectoryPath);

                //将文件系统对象添加到总的文件字典中
                foreach (string key in subDictionary.Keys)
                {
                    if (!fileEntityDictionary.ContainsKey(key))//检测重复项
                    {
                        fileEntityDictionary.Add(key, subDictionary[key]);
                    }
                }
            }
            return fileEntityDictionary;
        }
        /// <summary>
        /// 获取所有文件系统对象
        /// </summary>
        /// <param name="source">源路径</param>
        /// <param name="topDirectory">顶级文件夹</param>
        /// <returns>字典中Key为完整路径，Value为文件(夹)名称</returns>
        private static Dictionary<string, string> GetAllFileSystemEntities(string source, string topDirectory)
        {
            Dictionary<string, string> entitiesDictionary = new Dictionary<string, string>();
            entitiesDictionary.Add(source, source.Replace(topDirectory, ""));

            if (Directory.Exists(source))
            {
                //一次性获取下级所有目录，避免递归
                string[] directories = Directory.GetDirectories(source, "*.*", SearchOption.AllDirectories);
                foreach (string directory in directories)
                {
                    entitiesDictionary.Add(directory, directory.Replace(topDirectory, ""));
                }

                string[] files = Directory.GetFiles(source, "*.*", SearchOption.AllDirectories);
                foreach (string file in files)
                {
                    entitiesDictionary.Add(file, file.Replace(topDirectory, ""));
                }
            }

            return entitiesDictionary;
        }
        #endregion
    }


}
